/**
 * @file Netlist.cc
 * @author Xiaoze Lin (linxiaoze96@gmail.com)
 * @brief The implementation of netlists classes.
 * @version 0.1
 *
 * @copyright Copyright (c) 2024
 *
 */
#include "Netlist.hh"

namespace ifsim {

/**
 * @brief: Construct std cell node class.
 */
StdCellandIONode::StdCellandIONode() : _stdId(-1), _outPortCnt(0), _inPortCnt(0)
{
  ;
}

/**
 * @brief: Destruct std cell node class.
 */
StdCellandIONode::~StdCellandIONode()
{
  ;
}

/**
 * @brief: Construct std cell netlist class.
 */
StdCellNetlist::StdCellNetlist() : _numNodes(0), _numPIs(0), _numPOs(0), _numPIOs(0), _numCells(0)
{
  ;
}

/**
 * @brief: Destruct std cell netlist class.
 */
StdCellNetlist::~StdCellNetlist()
{
  ;
}

/**
 * @brief: Comupute std netlist statistics.
 */
void StdCellNetlist::computeCircuitStatistics()
{
  _numNodes = _node.size();
  for (int i = 0; i < (int) _node.size(); i++) {
    const StdCellandIONode& gat = _node[i];
    switch (gat.type()) {
      case STD_INPUT: {
        _numPIs++;
        break;
      }
      case STD_OUTPUT: {
        _numPOs++;
        break;
      }
      case STD_INOUT: {
        _numPIOs++;
        break;
      }
      case STD_CELL: {
        _numCells++;
        break;
      }
      default: {
        break;
      }
    }
  }
}

void StdCellNetlist::printCktStatistics() const
{
  std::cout << "\033[49;32mStandard Cell Netlist Information:\n";
  std::cout << TwoSpace << numPIs() << " inputs\n"
            << TwoSpace << numPOs() << " outputs\n"
            << TwoSpace << numPIOs() << " inouts\n"
            << TwoSpace << numCells() << " cells \033[0m" << std::endl;
}

/**
 * @brief: Construct primitive gate class.
 */
PrimitiveGate::PrimitiveGate() : _idStd(-1), _idSim(-1), _idPinname(-1), _level(-1), _netName(""), _outputMapping(-1)
{
  ;
}

/**
 * @brief: Construct primitive gate class with some parameters initialization.
 */
PrimitiveGate::PrimitiveGate(int gid, int lev, GType t, const std::vector<int>& preds_in, const std::vector<int>& succs_in)
    : Gate(gid, t, preds_in, succs_in), _idStd(-1), _idSim(-1), _idPinname(-1), _level(lev), _netName("")
{
  ;
}

/**
 * @brief: Destruct simulation gate class.
 */
PrimitiveGate::~PrimitiveGate()
{
  ;
}

/**
 * @brief: Construct simulation gate class.
 */
SimGate::SimGate() : Gate(-1, T_JUNK), _level(-1), _gmvIndex(-1), _netName(""), _isScanCell(false)
{
  ;
}

/**
 * @brief: Destruct simulation gate class.
 */
SimGate::~SimGate()
{
  ;
}

/**
 * @brief: Construct primitive netlist class.
 */
PrimitiveNetlist::PrimitiveNetlist()
    : Netlist(PRIMITIVE_NETLIST),
      _numNodes(0),
      _maxLevel(0),
      _numPIs(0),
      _numTIEs(0),
      _numPOs(0),
      _numDelayEs(0),
      _numDffs(0),
      _numDLatches(0),
      _numFBBUFs(0)
{
  ;
}

/**
 * @brief: Destruct primitive netlist class.
 */
PrimitiveNetlist::~PrimitiveNetlist()
{
  ;
}

/**
 * @brief: Primitive netlist clear function.
 */
void PrimitiveNetlist::clear()
{
  _nodes.clear();
  _clockIdAndOffstates.clear();
  _numNodes = 0;
  _maxLevel = 0;
  _numPIs = 0;
  _numTIEs = 0;
  _numPOs = 0;
  _numDelayEs = 0;
  _numDffs = 0;
  _numDLatches = 0;
  _numFBBUFs = 0;
}

/**
 * @brief: Compute pri netlist statistics.
 */
void PrimitiveNetlist::computeCircuitStatistics()
{
  _numNodes = (int) _nodes.size();
  for (int i = 0; i < (int) _nodes.size(); i++) {
    const PrimitiveGate& gat = _nodes[i];
    switch (gat.type()) {
      case T_INPUT: {
        _numPIs++;
        break;
      }
      case T_TIE1:
      case T_TIE0:
      case T_TIEX:
      case T_TIEZ: {
        _numTIEs++;
        break;
      }
      case T_OUTPUT: {
        _numPOs++;
        break;
      }
      case T_DFF_PRI: {
        _numDffs++;
        break;
      }
      case T_DELAY_ELEMENT: {
        _numDelayEs++;
        break;
      }
      case T_BUF_FB: {
        _numFBBUFs++;
        break;
      }
      case T_DLA_PRI: {
        _numDLatches++;
        break;
      }
      default: {
        break;
      }
    }
  }
  if (numDelayElements() && (numDffs() + numDLatches())) {
    LOG_ERROR << "delay elements should not coexist with D flip flops or latches";
  }
}

/**
 * @brief: Print pri netlist statistics.
 */
void PrimitiveNetlist::printCktStatistics(bool verbose) const
{
  std::cout << "\033[49;32mPrimitive Netlist Information:\n";
  std::cout << TwoSpace << numPIs() << " inputs\n"
            << TwoSpace << numPOs() << " outputs\n"
            << TwoSpace << numDelayElements() << " delay elements\n"
            << TwoSpace << numDffs() << " D flip flops\n"
            << TwoSpace << numDLatches() << " D latches\n"
            << TwoSpace << numFBBUFs() << " feedback buffers\n"
            << TwoSpace << numNodes() << " primitive gates\n"
            << TwoSpace << maxLevel() << " max level \033[0m" << std::endl;
}

/**
 * @brief: Split tie1 and tie0 nodes, also reduce fanout to no more than max fanout
 */
void PrimitiveNetlist::splitTieNodes(int max_fanout)
{
  // after generating PrimitiveNetlist, the last two nodes might be of type TIE0/1.
  std::stack<PrimitiveGate> tieNodes;
  auto it = _nodes.rbegin();
  while (it != _nodes.rend()) {
    GType ntype = it->type();
    if (ntype != T_TIE1 && ntype != T_TIE0) {
      break;
    }
    tieNodes.push(*it);
    std::advance(it, 1);      // it = it + 1;
    _nodes.erase(it.base());  // erase for the moment, add it back later, because one tie1 may be split to two
  }

  int num = (int) _nodes.size() - 1;
  while (!tieNodes.empty()) {
    PrimitiveGate node = tieNodes.top();
    tieNodes.pop();
    GType ntype = node.type();
    std::vector<int> succs = node.succs();
    int cnt = 0;
    PrimitiveGate* newNode;
    std::vector<int> newSuccs;
    for (auto ptr = succs.begin(); ptr != succs.end(); ++ptr) {
      if (0 == cnt) {  // add a new tie node (can be the old one or a new one because of touching max fanout)
        newNode = new PrimitiveGate;
        newNode->gid(++num);
        newNode->type(ntype);
        std::vector<int> temp_input_mapping = {-1};  // set input mapping
        newNode->setInputMapping(temp_input_mapping);
        if (ntype == T_TIE0) {
          newNode->stdCellInstName("1'b0");  // set names
          newNode->netName("TIE0");
        } else {
          newNode->stdCellInstName("1'b1");  // set names
          newNode->netName("TIE1");
        }
        addNode(*newNode);
        delete newNode;
      }
      newSuccs.push_back(*ptr);
      ++cnt;

      // modify the connection of the successors of the tie node
      std::vector<int> predv = _nodes[*ptr].preds();
      for (int& pid : predv) {
        if (pid == node.gid()) {
          pid = num;
        }
      }
      _nodes[*ptr].preds(predv);

      if (max_fanout == cnt) {  // reduce fanout
        cnt = 0;
        _nodes.back().succs(newSuccs);
        newSuccs.clear();
      }
    }
    if (!newSuccs.empty()) {
      _nodes.back().succs(newSuccs);
    }
  }
}

void LevelizedNetlist::populateNetlistVec()
{
  for (int g = 0; g < numNodes(); g++) {
    const SimGate& gat = constGate(g);

    _types.push_back(gat.type());

    _levels.push_back(gat.level());
    // assert( gat.level() < USHRT_MAX );
    if (0 == gat.numPreds()) {  // special case when numPred is 0
      _predFirst.push_back(-1);
    }
    for (int p = 0; p < gat.numPreds(); p++) {
      if (0 == p) {  // first predecessor
        _predFirst.push_back((int) _preds.size());
      }
      int pred_id = gat.predsC0()[p];
      _preds.push_back(pred_id);  // ( gat.predC0()[p] );
    }
    if (0 == gat.numSuccs()) {  // special case when numSucc is 0
      _succFirst.push_back(-1);
    }
    for (int s = 0; s < gat.numSuccs(); s++) {
      if (0 == s) {  // first successor
        _succFirst.push_back((int) _succs.size());
      }
      int succ_id = gat.succs()[s];
      _succs.push_back(succ_id);  // ( gat.succs()[s] );
    }
  }

  // Adjust for last node such that length of _predFirst and _succFirst
  // is 'numNodes() + 1' ???? check validity of the adjustment
  _predFirst.push_back((int) _preds.size());
  _succFirst.push_back((int) _succs.size());
  assert(_predFirst.size() == _succFirst.size());
  assert(_succs.size() == _preds.size());
  // Adjust the size of _types and _levels to be the same as _predFirst
  //   and _succFirst (to avoid out of bound access in ocl kernel )
  _types.push_back(0);
  _levels.push_back(0);
  // scan backward to adjust those predFirst[] and succFirst[] with value -1
  int curr_preds_index = -1;
  int curr_succs_index = -1;
  for (int i = (int) (_predFirst.size() - 1); i >= 0; i--) {
    // Change predFirst[i] of -1 to its vector upperbound
    if (-1 == _predFirst[i]) {
      _predFirst[i] = curr_preds_index;  // (int)_preds.size();
    } else {
      curr_preds_index = _predFirst[i];
    }
    if (-1 == _succFirst[i]) {
      _succFirst[i] = curr_succs_index;  // (int)_succs.size();
    } else {
      curr_succs_index = _succFirst[i];
    }
  }

  // shrink_to_fit to save memory usage
  _types.shrink_to_fit();
  _levels.shrink_to_fit();
  _preds.shrink_to_fit();
  _predsPtr = &(_preds[0]);
  _predFirst.shrink_to_fit();
  _succs.shrink_to_fit();
  _succsPtr = &(_succs[0]);
  _succFirst.shrink_to_fit();

  for (int g = 0; g < numNodes(); g++) {
    const SimGate& gat = constGate(g);
    // populate vector of ocl_gate for ocl-based simulation
    OclGate ocl_gate;
    ocl_gate.gtype = gat.type();
    ocl_gate.level = gat.level();
    ocl_gate.numPreds = gat.numPreds();
    ocl_gate.numSuccs = gat.numSuccs();
    ocl_gate.offsetPreds = _predFirst[g];
    ocl_gate.offsetSuccs = _succFirst[g];
    _oclGates.push_back(ocl_gate);
  }
  _oclGates.shrink_to_fit();
  _oclGatesPtr = &(_oclGates[0]);
}

/**
 * @brief: Traverse all pri nodes and insert buffers if touching max fanout.
 */
void PrimitiveNetlist::reduceFanout(int max_fanout)
{
  for (int g = 0; g < (int) _nodes.size(); g++) {
    PrimitiveGate& node = _nodes[g];
    if (node.numSuccs() > max_fanout) {
      insertBuffersAfterNode(g, max_fanout);
    }
  }
}

/**
 * @brief: Insert buffers after node.
 */
void PrimitiveNetlist::insertBuffersAfterNode(int gid, int max_fanout)
{
  PrimitiveGate node = _nodes[gid];
  int num = (int) _nodes.size() - 1;
  int num_succs = node.numSuccs();
  std::vector<int> succs = node.succs();
  std::vector<int> new_succs;

  while (num_succs > max_fanout) {  // cycles: log(group, num_succs)
    int tmp = num_succs / max_fanout;
    num_succs = (num_succs % max_fanout) ? tmp + 1 : tmp;  // need one more buffer
    new_succs.clear();
    for (int pi = 0; pi < num_succs; ++pi) {  // totally, num_succs groups
      PrimitiveGate* buff_node = new PrimitiveGate;
      buff_node->gid(++num);
      new_succs.push_back(num);
      buff_node->type(T_BUF);
      std::vector<int> buff_succs;
      std::vector<int> buff_preds;
      for (int i = 0; i < max_fanout; ++i) {  // one group
        int tmpindex = max_fanout * pi + i;
        if (tmpindex >= (int) succs.size()) {
          break;
        }
        buff_succs.push_back(succs[tmpindex]);
      }
      buff_node->succs(buff_succs);
      buff_preds.push_back(gid);
      buff_node->preds(buff_preds);
      std::vector<int> temp_input_mapping = {-1};  // set input mapping
      buff_node->setInputMapping(temp_input_mapping);
      buff_node->stdCellInstName("ADD_BUF");  // set names
      buff_node->netName("ADD_BUF_NET");
      addNode(*buff_node);

      // update the connection of the buffer's successors
      for (int sid : buff_succs) {
        std::vector<int> predv = _nodes[sid].preds();
        for (int& pid : predv) {
          if (pid == gid) {
            pid = num;
          }
        }
        _nodes[sid].preds(predv);
      }
      delete buff_node;
    }
    succs = new_succs;
  }
  _nodes[gid].succs(succs);
}

/**
 * @brief: Insert TIEX after node.
 */
void PrimitiveNetlist::insertTIEXAfterNode(int pred_id, int succ_id)
{
  int num = (int) _nodes.size() - 1;

  PrimitiveGate* tiex_node = new PrimitiveGate;
  tiex_node->gid(++num);
  tiex_node->type(T_TIEX);

  std::vector<int> tiex_succs, tiex_preds;  // set succs and preds
  tiex_succs.push_back(succ_id);
  tiex_node->succs(tiex_succs);
  tiex_preds.push_back(pred_id);
  tiex_node->preds(tiex_preds);

  std::vector<int> temp_input_mapping = {-1};  // set input mapping
  tiex_node->setInputMapping(temp_input_mapping);
  tiex_node->stdCellInstName("ADD_TIEX");  // set names
  tiex_node->netName("ADD_TIEX_NET");
  addNode(*tiex_node);
  delete tiex_node;

  // update the pred of the succ_id node
  std::vector<int> predv = _nodes[succ_id].preds();
  for (int& pid : predv) {
    if (pid == pred_id) {
      pid = num;
    }
  }
  _nodes[succ_id].preds(predv);
  // update the succ of the pred_id node
  std::vector<int> succv = _nodes[pred_id].succs();
  for (int& sid : succv) {
    if (sid == succ_id) {
      sid = num;
    }
  }
  _nodes[pred_id].succs(succv);
}

/**
 * @brief: Insert feedback buffer after node.
 */
void PrimitiveNetlist::insertFeedbackBufAfterNode(int pred_id, int succ_id)
{
  int num = (int) _nodes.size() - 1;

  PrimitiveGate* fbbuf_node = new PrimitiveGate;
  fbbuf_node->gid(++num);
  fbbuf_node->type(T_BUF_FB);

  std::vector<int> fbbuf_succs, fbbuf_preds;  // set succs and preds
  fbbuf_succs.push_back(succ_id);
  fbbuf_node->succs(fbbuf_succs);
  fbbuf_preds.push_back(pred_id);
  fbbuf_node->preds(fbbuf_preds);

  std::vector<int> temp_input_mapping = {-1};  // set input mapping
  fbbuf_node->setInputMapping(temp_input_mapping);
  fbbuf_node->stdCellInstName("ADD_BUF_FB");  // set names
  fbbuf_node->netName("ADD_BUF_FB_NET");

  // update the pred of the succ_id node
  bool update_pred = false;
  bool update_succ = false;
  std::vector<int> predv = _nodes[succ_id].preds();
  for (int& pid : predv) {
    if (pid == pred_id) {
      pid = num;
      update_pred = true;
    }
  }
  _nodes[succ_id].preds(predv);
  // update the succ of the pred_id node
  std::vector<int> succv = _nodes[pred_id].succs();
  for (int& sid : succv) {
    if (sid == succ_id) {
      sid = num;
      update_succ = true;
    }
  }
  _nodes[pred_id].succs(succv);
  if ((!update_pred) && (!update_succ)) {
    // maybe already insert feedback buffer in the same location becasuse of complex loops
    LOG_WARNING << "pred and succ are all not update when insert feedback buffer";
  } else if (!update_pred) {
    LOG_ERROR << "only pred not update, something wrong when inserting feedback buffer";
  } else if (!update_succ) {
    LOG_ERROR << "only succ not update, something wrong when inserting feedback buffer";
  } else {
    addNode(*fbbuf_node);
    delete fbbuf_node;
  }
}

/**
 * @brief: Insert buffer between two successive DFFs.
 */
void PrimitiveNetlist::insertBuffersBetweenDFFs()
{
  for (auto ptr_1ff = _nodes.begin(); ptr_1ff != _nodes.end(); ++ptr_1ff) {
    if (ptr_1ff->type() != T_DFF_PRI) {
      continue;
    }
    int num = _nodes.size();
    int id_1ff = ptr_1ff->gid();
    std::vector<int> succs_1ff = ptr_1ff->succs();

    for (int& sid : succs_1ff) {
      PrimitiveGate* ptr_2ff = &gate(sid);
      if (ptr_2ff->type() != T_DFF_PRI) {
        continue;
      }

      // find two DFF nodes that are successive, add buffer
      PrimitiveGate* buff_node = new PrimitiveGate;
      buff_node->gid(num);  // add to the end of _nodes
      buff_node->type(T_BUF);
      std::vector<int> buff_succs = {sid};  // build succs and preds
      std::vector<int> buff_preds = {id_1ff};
      buff_node->succs(buff_succs);
      buff_node->preds(buff_preds);
      std::vector<int> temp_input_mapping = {-1};  // set input mapping
      buff_node->setInputMapping(temp_input_mapping);
      buff_node->stdCellInstName("ADD_BUF");  // set names
      buff_node->netName("ADD_BUF_NET");
      addNode(*buff_node);
      delete buff_node;

      std::vector<int> preds_2ff = ptr_2ff->preds();
      for (int& pid : preds_2ff) {
        if (pid == id_1ff) {
          pid = num;
          break;
        }
      }
      ptr_2ff->preds(preds_2ff);

      sid = num++;  // update sid, and update num because there may be multi succs are DFF
    }
    ptr_1ff->succs(succs_1ff);
  }
}

/**
 * @brief: Construct levelized netlist class.
 */
LevelizedNetlist::LevelizedNetlist()
    : _numNodes(0),
      _junk(0),
      _numPIs(0),
      _numTIEs(0),
      _numPOs(0),
      _numSEs(0),
      _numDelayEs(0),
      _numDffs(0),
      _numDLatches(0),
      _numFBBUFs(0),
      _firstNodePI(-1),
      _firstNodePO(-1),
      _firstNodeTIE(-1),
      _firstNodeDE(-1),
      _firstNodeDff(-1),
      _firstNodeFBBUF(-1),
      _firstNodeLatch(-1),
      _numLevels(0),
      _maxLevelWidth(0),
      _gmvArraySize(0),
      _numNodesPerType((int) T_LAST_GTYPE, 0),
      _predsPtr(NULL),
      _succsPtr(NULL)
{
  ;
}

/**
 * @brief: Destruct levelized netlist class.
 */
LevelizedNetlist::~LevelizedNetlist()
{
  ;
}

/**
 * @brief: Levelized netlist clear function.
 */
void LevelizedNetlist::clear()
{
  _numNodes = 0;
  _junk = 0;
  _numPIs = 0;
  _numTIEs = 0;
  _numPOs = 0;
  _numDelayEs = 0;
  _numDffs = 0;
  _numDLatches = 0;
  _numFBBUFs = 0;
  _firstNodePI = -1;
  _firstNodePO = -1;
  _firstNodeTIE = -1;
  _firstNodeDE = -1;
  _firstNodeDff = -1;
  _firstNodeFBBUF = -1;
  _firstNodeLatch = -1;
  _numLevels = 0;
  _maxLevelWidth = 0;
  _gmvArraySize = 0;
  std::fill(_numNodesPerType.begin(), _numNodesPerType.end(), 0);
  _numNodesInLevel.clear();
  _firstNodeInLevel.clear();
  _maxInputsInLevel.clear();
  _andIsOnInLevel.clear();
  _orIsOnInLevel.clear();
  _xorIsOnInLevel.clear();
  _invIsOnInLevel.clear();
  _nodes.clear();
  _types.clear();
  _levels.clear();
  _preds.clear();
  _predFirst.clear();
  _succs.clear();
  _succFirst.clear();
}

/**
 * @brief: Compute levelized netlist statistics.
 */
int LevelizedNetlist::computeCircuitStatistics()
{
  numNodes((int) _nodes.size());
  junk(10);
  int max_level = 0;
  for (int i = 0; i < (int) _nodes.size(); i++) {
    const SimGate& gat = _nodes[i];
    if (gat.level() > max_level) {
      max_level = _nodes[i].level();
    }
    switch (gat.type()) {
      case T_INPUT: {
        if (0 == _numPIs) {
          _firstNodePI = i;
        }
        _numPIs++;
        break;
      }
      case T_TIE1:
      case T_TIE0:
      case T_TIEX:
      case T_TIEZ: {
        if (0 == _numTIEs) {
          _firstNodeTIE = i;
        }
        _numTIEs++;
        break;
      }
      case T_OUTPUT: {
        if (0 == _numPOs) {
          _firstNodePO = i;
        }
        _numPOs++;
        break;
      }
      case T_DFF_PRI: {
        if (0 == _numDffs) {
          _firstNodeDff = i;
        }
        _numDffs++;
        break;
      }
      case T_DELAY_ELEMENT: {
        if (0 == _numDelayEs) {
          _firstNodeDE = i;
        }
        _numDelayEs++;
        break;
      }
      case T_BUF_FB: {
        if (0 == _numFBBUFs) {
          _firstNodeFBBUF = i;
        }
        _numFBBUFs++;
        break;
      }
      case T_DLA_PRI: {
        if (0 == _numDLatches) {
          _firstNodeLatch = i;
        }
        _numDLatches++;
        break;
      }
      default: {
        break;
      }
    }
  }
  _numLevels = max_level + 1;
  // set _firstNodeDff to non-existent numNodes() such that it enables
  //   quick computation of gmvIndex on the fly even when there is no DFF.
  if (0 == numDffs()) {
    _firstNodeDff = numNodes();
  }
  _gmvArraySize = numNodes() + numDffs();  // a DFF takes two copies of GMVs
  if (numDelayElements() && (numDffs() + numDLatches())) {
    LOG_ERROR << "delay elements should not coexist with D flip flops or latches";
    return STATUS_ERROR;
  }
  _numSEs = numDelayElements() + numDffs() + numDLatches();

  std::fill(_numNodesPerType.begin(), _numNodesPerType.end(), 0);
  _numNodesInLevel.resize(numLevels());
  _firstNodeInLevel.resize(numLevels());
  _maxInputsInLevel.resize(numLevels());
  _andIsOnInLevel.resize(numLevels());
  _orIsOnInLevel.resize(numLevels());
  _xorIsOnInLevel.resize(numLevels());
  _invIsOnInLevel.resize(numLevels());
  for (int l = 0; l < numLevels(); l++) {
    _numNodesInLevel[l] = 0;
    _firstNodeInLevel[l] = -1;
    _maxInputsInLevel[l] = 0;
    _andIsOnInLevel[l] = 0;
    _orIsOnInLevel[l] = 0;
    _xorIsOnInLevel[l] = 0;
    _invIsOnInLevel[l] = 0;
  }
  for (int i = 0; i < numNodes(); i++) {
    int lev = gate(i).level();
    _numNodesInLevel[gate(i).level()]++;
    if (-1 == _firstNodeInLevel[gate(i).level()]) {
      _firstNodeInLevel[gate(i).level()] = i;
    }
    if (gate(i).numPreds() > _maxInputsInLevel[lev]) {
      _maxInputsInLevel[lev] = gate(i).numPreds();
    }
    _numNodesPerType[(int) gate(i).type()]++;
    if (gate(i).type() == T_AND || gate(i).type() == T_NAND) {
      _andIsOnInLevel[lev] = 1;
    }
    if (gate(i).type() == T_OR || gate(i).type() == T_NOR) {
      _orIsOnInLevel[lev] = 1;
    }
    if (gate(i).type() == T_XOR || gate(i).type() == T_XNOR) {
      _xorIsOnInLevel[lev] = 1;
    }
    if (gate(i).type() == T_NOT || gate(i).type() == T_NOR || gate(i).type() == T_NAND || gate(i).type() == T_XNOR) {
      _invIsOnInLevel[lev] = 1;
    }
  }
  _maxLevelWidth = *(std::max_element(_numNodesInLevel.begin(), _numNodesInLevel.end()));

  populateGateGmvIndex();  // pre-compute gmv index for each gate
  return STATUS_OK;
}

/**
 * @brief: populate gate good machine value index.
 */
void LevelizedNetlist::populateGateGmvIndex()
{
  int gmv_i = 0;  // current gmv index
  for (int g = 0; g < numNodes(); g++) {
    SimGate& gat = _nodes[g];
    if (gat.type() == T_DFF_PRI) {  // master-slave DFF need two gmvs
      gat.gmvIndex(gmv_i);
      gmv_i += 2;
    } else {
      gat.gmvIndex(gmv_i);
      gmv_i += 1;
    }
  }
}

/**
 * @brief: Sim to find tie gates.
 */
void LevelizedNetlist::simToFindTieGates()
{
  std::queue<int> event_queue;
  std::vector<GateValue> gate_vals;
  gate_vals.assign(_numNodes + _numDffs, VAL_X);  // init gate_vals

  // lambda function
  auto push_succs = [](SimGate snode, std::queue<int>& event_queue) {
    for (int succ_id : snode.succs()) {
      event_queue.push(succ_id);
    }
  };

  // push tie nodes' succs into event queue
  for (int tie_id = 0; tie_id < _numTIEs; ++tie_id) {
    SimGate snode = _nodes[tie_id + _firstNodeTIE];
    if ((snode.type() != T_TIE0) && (snode.type() != T_TIE1)) {  // tie nodes are the only gates at this stage
      continue;
    }
    GateValue val = (snode.type() == T_TIE0) ? VAL_ZERO : VAL_ONE;
    gate_vals[snode.gid()] = val;  // set tie node val
    storeTieGates(snode.gid(), val);
    push_succs(snode, event_queue);
  }

  // event-driven logic simulation
  while (!event_queue.empty()) {
    int gid = event_queue.front();
    event_queue.pop();
    SimGate snode = _nodes[gid];
    GateValue old_val = gate_vals[gid];
    GateValue new_val = VAL_X;
    gateEval(snode, gate_vals, new_val);
    if (old_val != new_val && new_val != VAL_X) {
      gate_vals[gid] = new_val;
      storeTieGates(gid, new_val);
      push_succs(snode, event_queue);
    }
  }
}

/**
 * @brief: store gates with tie value.
 */
void LevelizedNetlist::storeTieGates(int gid, GateValue val)
{
  if (val == VAL_ONE) {
    _tie1.insert(gid);
  } else if (val == VAL_ZERO) {
    _tie0.insert(gid);
  } else if (val == VAL_Z) {
    _tiez.insert(gid);
  } else {
    std::cout << ErrColor << "Error: store tie node " << gid << " with val: " << val << "." << NormalColor << std::endl;
  }
}

/**
 * @brief: Gate evaluation based on LUT.
 */
void LevelizedNetlist::gateEval(const SimGate snode, std::vector<GateValue> gate_vals, GateValue& val)
{
  GType type = snode.type();
  switch (type) {
    case T_NOT:
    case T_BUF:
    case T_OUTPUT: {
      GateValue pred_val = gate_vals[snode.pred(0)];
      val = TruthTable1[type][pred_val];
      break;
    }
    case T_TRISTATEBUF:
    case T_TRISTATEINV: {  // (OE, I)
      GateValue oe_val = gate_vals[snode.pred(0)];
      GateValue in_val = gate_vals[snode.pred(1)];
      val = (oe_val == VAL_ONE) ? in_val : (oe_val == VAL_ZERO) ? VAL_Z : VAL_X;
      val = (type == T_TRISTATEINV) ? TruthTable1[T_NOT][val] : val;
      break;
    }
    case T_AND:
    case T_NAND:
    case T_OR:
    case T_NOR:
    case T_XOR:
    case T_XNOR: {
      GateValue pred_val = gate_vals[snode.pred(0)];
      GateValue pred_val_n = gate_vals[snode.pred(1)];
      val = TruthTable2[type][pred_val][pred_val_n];
      for (int pid = 2; pid < snode.numPreds(); ++pid) {
        pred_val_n = gate_vals[snode.pred(pid)];
        val = TruthTable2[type][val][pred_val_n];
      }
      break;
    }
    case T_MUX_2: {  // (SEL, I1, I2)
      GateValue sel_val = gate_vals[snode.pred(0)];
      GateValue in1_val = gate_vals[snode.pred(1)];
      GateValue in2_val = gate_vals[snode.pred(2)];
      if (sel_val == VAL_ZERO) {
        val = in1_val;
      } else if (sel_val == VAL_ONE) {
        val = in2_val;
      } else {
        if (in1_val == in2_val) {
          val = in1_val;
        } else {
          val = VAL_X;
        }
      }
      break;
    }
    case T_DLA_PRI: {
      break;
    }
    case T_DFF_PRI: {
      break;
    }
    default: {
      LOG_ERROR << "unsupported gate type " << LUT_GTypeToName[type];
      break;
    }
  }
}

/**
 * @brief: print sim netlist statistics.
 */
void LevelizedNetlist::printCktStatistics(bool verbose) const
{
  std::cout << "\033[49;32mSimulation Netlist Information:\n";
  std::cout << TwoSpace << numPIs() << " inputs\n"
            << TwoSpace << numPOs() << " outputs\n"
            << TwoSpace << numDelayElements() << " delay elements\n"
            << TwoSpace << numDffs() << " D flip flops\n"
            << TwoSpace << numDLatches() << " D latches\n"
            << TwoSpace << numFBBUFs() << " feedback buffers\n"
            << TwoSpace << numNodes() << " simulation gates\n"
            << TwoSpace << numLevels() << " total levels\n"
            << TwoSpace << maxLevelWidth() << " max level width" << std::endl;
  std::cout << TwoSpace << "Statistics of gate types:\n";
  for (int i = 0; i < (int) T_LAST_GTYPE; i++) {
    if (_numNodesPerType[i] > 0) {
      std::cout << TwoSpace << TwoSpace << LUT_GTypeToName[i] << ": " << _numNodesPerType[i] << std::endl;
    }
  }
  std::cout << TwoSpace << "Gates per level:" << std::endl;
  for (int l = 0; l < numLevels(); l++) {
    char str[1024];
    sprintf(str, "\033[49;32m    %4d : %6d   (1st_node: %10d)", l, _numNodesInLevel[l], _firstNodeInLevel[l]);
    std::cout << str << "\033[0m" << std::endl;
  }
  if (verbose) {
    std::cout << "\n" << TwoSpace << "Netlist for Opencl kernel:\n";
    std::cout << TwoSpace << _types.size() << " : size of types array\n"
              << TwoSpace << _levels.size() << " : size of levels array\n"
              << TwoSpace << _preds.size() << " : size of predecessors array\n"
              << TwoSpace << _predFirst.size() << " : size of predFirst array\n"
              << TwoSpace << _succs.size() << " : size of successors array\n"
              << TwoSpace << _succFirst.size() << " : size of succFirst array\n";
  }
}
}  // namespace ifsim